pytestのfixtureの実行順序を調べてみた
こんにちは、CX事業本部の夏目です。
案件ではPythonを使ってLambdaを実装しているのですが、テストにpytestを使用しています。
pytestにはsetup/teadownの機能としてfixtureなるものがあるのですが、使用すると記載する場所で実行順序が異なるので調べてみました。
pytestのfixture
fixutureとはテスト関数の実行前後に実行される関数です。
テストデータを用意したりすることに使えます。
テストファイル内で定義することもできるし、conftest.py
というファイルで定義すれば複数のテストファイルで使用することができる。
調べてみた
概要
conftest.py
ので定義したfixtureと、テストファイルで定義したfixtureを使う- 各fixtureで同一ファイルに追記を行うことで、順番を特定する
- スコープは全て
function
とする (シンプルにするため) - テストに対して使用するfixtureを設定する箇所は次のものを想定
conftest.py
に定義したautouseなfixture- テストファイルに定義したautouseなfixture
- テストクラスの
@pytest.mark.usefixture()
で指定したfixture - テスト関数の
@pytest.mark.usefixture()
で指定したfixture - テスト関数の引数で指定したfixture
コード
import pytest def add_message(msg: str): open(f'result.txt', mode='a').write(f'{msg}\n') @pytest.fixture(scope='function', autouse=True) def fixture_auto_use_conftest(): mes = 'auto_use_conftest' add_message(f'start {mes}') yield # fixtureはyieldで区切ると、yieldより前の処理をテストの前に、yieldより後の処理をテストの後に、実行する add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_class_usefixtures(): mes = 'class_use_fixture' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_class_indirect_and_usefixtures(request): mes = 'class_indirect_and_usefixtures' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_class_indirect(request): mes = 'class_indirect' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_func_indirect_and_usefixtures(request): mes = 'func_indirect_and_usefixtures' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_func_indirect(request): mes = 'func_indirect' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_func_usefixtures(): mes = 'func_usefixtures' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_func_call_01(): mes = 'func_call_01' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.fixture(scope='function') def fixture_func_call_02(): mes = 'func_call_02' add_message(f'start {mes}') yield add_message(f'end {mes}')
import pytest from conftest import add_message @pytest.fixture(scope='function', autouse=True) def fixture_auto_use_test_file(): mes = 'autouse_test_file' add_message(f'start {mes}') yield add_message(f'end {mes}') @pytest.mark.parametrize( 'fixture_class_indirect_and_usefixtures, fixture_class_indirect', [ (None, None) ], indirect=True ) @pytest.mark.usefixtures('fixture_class_usefixtures', 'fixture_class_indirect_and_usefixtures') class TestMain(object): @pytest.mark.parametrize( 'fixture_func_indirect_and_usefixtures, fixture_func_indirect', [ (None, None) ], indirect=True ) @pytest.mark.usefixtures( 'fixture_func_usefixtures', 'fixture_func_indirect_and_usefixtures' ) def test_pass(self, fixture_func_call_01, fixture_class_indirect, fixture_func_indirect, fixture_func_call_02): add_message('====')
実行結果
start auto_use_conftest start autouse_test_file start func_usefixtures start func_indirect_and_usefixtures start class_use_fixture start class_indirect_and_usefixtures start func_call_01 start class_indirect start func_indirect start func_call_02 ==== end func_call_02 end func_indirect end class_indirect end func_call_01 end class_indirect_and_usefixtures end class_use_fixture end func_indirect_and_usefixtures end func_usefixtures end autouse_test_file end auto_use_conftest
結論
parameterizeのindirectなども考えたけども結論はこれ。
注意が必要そうなのは、@pytest.mark.usefixture()
で指定したfixtureにおいて、関数で指定したfixtureの後にクラスで指定したfixtureが動くこと。
まとめ
pytestのfixtureの実行順序についてまとめてみました。
ここまで面倒な使い方をすることはないと思いますが、こういう順序で実行されるんだ程度に覚えておくと良さそうです。